﻿using System;
using System.Linq;
using System.Runtime.CompilerServices;
using RayDen.Library.Components.Color.Types;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;

namespace RayDen.RayEngine.Core.Types {

    public struct SampleBufferElem {
        public float screenX, screenY;
        public float PathWeight;
        public int pass;
        public RgbSpectrum radiance;
    };


    public struct SampleBufferElem<TRadiance>
        where TRadiance:IColorType
    {
        public float screenX, screenY;
        public float PathWeight;
        public int pass;
        public TRadiance radiance;
    };
    public class SampleBuffer<TRadiance>
        where TRadiance : IColorType
    {
        private SampleBufferElem<TRadiance>[] samples;
        private int currentFreeSample;


        public int NonBlackSamples
        {
            get { return samples.Count(item => !item.radiance.IsBlack()); }
        }

        public virtual int Size
        {
            get { return this.samples.Length; }
        }

        public SampleBuffer(int size)
        {
            samples = new SampleBufferElem<TRadiance>[size];
        }


        public SampleBufferElem<TRadiance>[] Samples
        {
            get
            {
                return samples;
            }
        }

        public void SplatSample(Sample sample, TRadiance rad, float pw = 1.0f)
        {
#if DEBUG
            //rad.AssertNaN();
#endif
            //try{
            if (currentFreeSample == samples.Length - 1)
                return;
            if (currentFreeSample < 0)
                return;
            samples[currentFreeSample] = new SampleBufferElem<TRadiance>()
            {
                pass = sample.pass,
                PathWeight = pw,
                radiance = rad,
                screenX = (float)sample.imageX,
                screenY = (float)sample.imageY
            };
            currentFreeSample++;

            /*}catch (Exception ex)

            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(currentFreeSample);
            }*/

            //samples[currentFreeSample].screenX = sample.imageX;
            //samples[currentFreeSample].screenY = sample.imageY;
            //samples[currentFreeSample].pass = sample.pass;
            //samples[currentFreeSample].PathWeight = pw;

#if SPECTRUM

            samples[currentFreeSample].radiance = new RgbSpectrum(rad.XYZ());
#else
            //samples[currentFreeSample].radiance = rad;

#endif
        }


        public void SplatSample(float imageX, float imageY, ref TRadiance rad, float pw = 1.0f, int weight = 1)
        {
            if (currentFreeSample == samples.Length - 1)
            {
                this.currentFreeSample = samples.Length + 1;
                return;
            }

            if (currentFreeSample > samples.Length)
            {
                return;
            }

            samples[currentFreeSample].screenX = imageX;
            samples[currentFreeSample].screenY = imageY;
            samples[currentFreeSample].pass = weight;
            samples[currentFreeSample].PathWeight = pw;
            samples[currentFreeSample].radiance = rad;
            currentFreeSample++;

        }

        public bool IsFull()
        {
            return (currentFreeSample >= samples.Length);
        }

        public void WriteMax()
        {
            // Console.WriteLine("Max radiance {0}", this.samples.Max(p=>p.radiance));
        }
        public int GetSampleCount()
        {
            return Math.Min(currentFreeSample, samples.Length - 1);
        }

        public void Reset()
        {
            var ln = samples.Length;
            if (samples == null)
                samples = new SampleBufferElem<TRadiance>[ln];
            else
            {
                for (int index = 0; index < samples.Length; index++)
                {
                    samples[index] = default(SampleBufferElem<TRadiance>);
                }
            }

            currentFreeSample = 0;
        }
    }




    public class SampleBuffer {
        private SampleBufferElem[] samples;
        private int currentFreeSample;


        public int NonBlackSamples {
            get { return samples.Count(item => !item.radiance.IsBlack()); }
        }

        public virtual int Size
        {
            get { return this.samples.Length; }
        }

        public SampleBuffer(int size) {
            samples = new SampleBufferElem[size];
        }


        public SampleBufferElem[] Samples {
            get {
                return samples;
            }
        }

        public void SplatSample(Sample sample, RgbSpectrum rad, float pw = 1.0f)
        {
#if DEBUG
            rad.AssertNaN();
#endif
            //try{
                if (currentFreeSample == samples.Length-1)
                    return;
                if (currentFreeSample < 0)
                    return;
                samples[currentFreeSample] = new SampleBufferElem()
                    {
                        pass = sample.pass,
                        PathWeight = pw,
                        radiance = rad,
                        screenX = (float)sample.imageX,
                        screenY = (float)sample.imageY
                    };
                currentFreeSample++;

            /*}catch (Exception ex)

            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(currentFreeSample);
            }*/

        //samples[currentFreeSample].screenX = sample.imageX;
            //samples[currentFreeSample].screenY = sample.imageY;
            //samples[currentFreeSample].pass = sample.pass;
            //samples[currentFreeSample].PathWeight = pw;

#if SPECTRUM

            samples[currentFreeSample].radiance = new RgbSpectrum(rad.XYZ());
#else
            //samples[currentFreeSample].radiance = rad;

#endif
        }



        public void SplatSample(Sample sample, float rad, int spectralIndex, float pw = 1.0f) {
#if DEBUG
            //rad.AssertNaN();
#endif
            currentFreeSample++;
            if (currentFreeSample >= samples.Length)
                return;
            samples[currentFreeSample].PathWeight = pw;
            samples[currentFreeSample].screenX = (float)sample.imageX;
            samples[currentFreeSample].screenY = (float)sample.imageY;
            samples[currentFreeSample].pass = sample.pass;
#if SPECTRUM

            samples[currentFreeSample].radiance = new RgbSpectrum(rad.XYZ());
#else
            if (Math.Abs(samples[currentFreeSample].radiance[spectralIndex] - 0f) > MathLab.SpectreEpsilon && spectralIndex < 2 && Math.Abs(samples[currentFreeSample].radiance[spectralIndex + 1] - 0f) < MathLab.SpectreEpsilon)
                samples[currentFreeSample].radiance[spectralIndex + 1] = rad;
            else
                if (Math.Abs(samples[currentFreeSample].radiance[spectralIndex] - 0f) > MathLab.SpectreEpsilon && spectralIndex == 2 && Math.Abs(samples[currentFreeSample].radiance[0] - 0f) < MathLab.SpectreEpsilon)
                {
                    samples[currentFreeSample].radiance[0] = rad;
                }
                else {
                    samples[currentFreeSample].radiance[spectralIndex] = rad;
                }
#endif
        }
        public void SplatSample(float imageX, float imageY,ref RgbSpectrum rad, float pw = 1.0f, int weight = 1) {
            if (currentFreeSample == samples.Length-1)
            {
                this.currentFreeSample = samples.Length + 1;
                return;
            }

            if (currentFreeSample > samples.Length)
            {
                return;
            }

            samples[currentFreeSample].screenX = imageX;
            samples[currentFreeSample].screenY = imageY;
            samples[currentFreeSample].pass = weight;
            samples[currentFreeSample].PathWeight = pw;
            samples[currentFreeSample].radiance = rad;
            currentFreeSample++;

        }

        public void SplatSample(double imageX, double imageY, ref RgbSpectrum rad, float pw = 1.0f, int weight = 1)
        {
            this.SplatSample((float)imageX, (float)imageY, ref rad, pw, weight);
        }

        public void SplatSample(float imageX, float imageY, RgbSpectrumInfo rad, float pw = 1.0f, int weight = 1)
        {
            if (currentFreeSample == samples.Length - 1)
            {
                this.currentFreeSample = samples.Length + 1;
                return;
            }

            if (currentFreeSample > samples.Length)
            {
                return;
            }

            samples[currentFreeSample].screenX = imageX;
            samples[currentFreeSample].screenY = imageY;
            samples[currentFreeSample].pass = weight;
            samples[currentFreeSample].PathWeight = pw;
            samples[currentFreeSample].radiance = (RgbSpectrum)rad;
            currentFreeSample++;

        }
        public bool IsFull() {
            return (currentFreeSample >= samples.Length);
        }

        public void WriteMax() {
            // Console.WriteLine("Max radiance {0}", this.samples.Max(p=>p.radiance));
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int GetSampleCount() {
            return Math.Min(currentFreeSample, samples.Length - 1);
        }

        public void Reset() {
            var ln = samples.Length;
            if (samples == null) 
                samples = new SampleBufferElem[ln];
            else
            {
                for (int index  = 0; index < samples.Length; index++)
                {

                    samples[index] = default (SampleBufferElem);
                }
            }

            currentFreeSample = 0;
        }
    }


    public class DeviceSampleBuffer<TBuffer> : SampleBuffer
    {
        protected TBuffer Buffer;

        public DeviceSampleBuffer(int size) : base(size)
        {

        }

        public TBuffer DeviceBuffer { get { return this.Buffer; } }
    }
    
}
